package com.whately.core.redis.verifycode;

import com.google.common.collect.Maps;
import com.whately.core.base.config.interceptor.RemoteIPInterceptor;
import com.whately.core.base.constant.WhatelyCoreCacheKeyEnum;
import com.whately.core.base.exception.ServiceError;
import com.whately.core.base.exception.ServiceException;
import com.whately.core.base.model.RedisKeyBuilder;
import com.whately.core.base.utils.MobileUtils;
import com.whately.core.base.verifycode.WhatelyVerifyCodeFacade;
import com.whately.core.base.constant.VerifyCodeTypeEnum;
import com.whately.core.base.verifycode.util.SmsGenerateResult;
import com.whately.core.base.verifycode.util.WhatelyVerifyCodeUtil;
import com.whately.core.base.vo.req.CheckSmsCodeCorrectReqVo;
import com.whately.core.redis.utils.RedisCacheHashUtils;
import com.whately.core.redis.utils.RedisCacheStringUtils;
import com.whately.core.redis.utils.RedisTemplateUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.concurrent.TimeUnit;

@Service
@Primary
@Slf4j
public class WhatelyVerifyCodeFacadeRedisProxy implements WhatelyVerifyCodeFacade {

    @Autowired
    private RedisCacheStringUtils redisCacheStringUtils;

    @Autowired
    private RedisCacheHashUtils redisCacheHashUtils;

    @Autowired
    private RedisTemplateUtils redisTemplateUtils;

    @Autowired
    private WhatelyVerifyCodeUtil whatelyVerifyCodeUtil;

    //手机验证码校验
    @Override
    public void checkSmsCodeWhenThroughMobile(CheckSmsCodeCorrectReqVo requestVo, VerifyCodeTypeEnum verifyCodeTypeEnum){
        String token = requestVo.getToken();
        String mobile = requestVo.getMobile();
        String verifyCode = requestVo.getVerifyCode();
        if(StringUtils.isBlank(token) ||StringUtils.isBlank(verifyCode)){
            log.error("checkWhenThroughMobile bad request parameter requestVo {}",requestVo);
            throw new ServiceException(ServiceError.REQUEST_PARAMETER_ERROR);
        }

        if (!MobileUtils.isPhone(mobile)) {
            log.error("BaseLoginHelper checkWhenLoginByMobileSmsCode bad request parameter requestVo {}", requestVo);
            throw new ServiceException(ServiceError.REQUEST_PARAMETER_ERROR.getCode(),"手机号格式不正确");
        }

        RedisKeyBuilder rkb = whatelyVerifyCodeUtil.smsCacheToken(verifyCodeTypeEnum,token);

        String redisValue = redisCacheHashUtils.get(rkb,"value");
        if(null==redisValue){
            log.error("checkWhenThroughMobile  token is expired requestVo {}",requestVo);
            throw new ServiceException(ServiceError.VERIFY_CODE_SMS_ERROR.getCode(),"短信验证码已失效，请重新获取");
        }

        //输入错误了，统一2分钟后失效时间
        long count = redisCacheHashUtils.incrementAndExpire(rkb,"count",1, WhatelyVerifyCodeUtil.SMS_DEFAULT_EXPIRE_MINUTES, TimeUnit.MINUTES);

        //验证码输入错误超过三次，则失效，需要重新输入图片验证码，然后重新获取验证码
        if(count>WhatelyVerifyCodeUtil.SMS_TRY_MAX_TIMES){
            redisTemplateUtils.delete(rkb);
            log.error("checkWhenThroughMobile  mobile or sms code not match with server requestVo {}",requestVo);
            throw new ServiceException(ServiceError.VERIFY_CODE_SMS_ERROR.getCode(),"短信验证失败，请重新获取");
        }

        if(!StringUtils.equals(redisValue,mobile+verifyCode)){
            log.error("checkWhenThroughMobile  mobile or sms code not match with server requestVo {}",requestVo);
            throw new ServiceException(ServiceError.VERIFY_CODE_SMS_ERROR.getCode(),"短信验证码错误");
        }
    }

    @Override
    public SmsGenerateResult sendSmsVerifyCode(VerifyCodeTypeEnum verifyCodeType,
                                               String mobile, int expireMinutes){
        SmsGenerateResult generateResult = whatelyVerifyCodeUtil.generateSmsCodeInfo();

        //把mobile+code缓存起来
        Map<String,String> redisMap= Maps.newHashMap();
        redisMap.put("count","0");
        redisMap.put("value",mobile+generateResult.getSmsCode());
        if(expireMinutes<=0){
            expireMinutes = WhatelyVerifyCodeUtil.SMS_DEFAULT_EXPIRE_MINUTES;
        }
        RedisKeyBuilder rkb = whatelyVerifyCodeUtil.smsCacheToken(verifyCodeType,generateResult.getToken());
        redisCacheHashUtils.putAllAndExpire(rkb,redisMap,expireMinutes,TimeUnit.MINUTES);

        return generateResult;
    }



    //校验验证码，通过解密token方式
    @Override
    public void checkImgVerifyCodeFromAesToken(VerifyCodeTypeEnum verifyCodeType,String token,String riskUniqueId,String imgVerifyCode){
        //检查token是否被使用了
        RedisKeyBuilder usedCkb = WhatelyCoreCacheKeyEnum.TOKEN_VERIFY_CODE_USED.obtainKeyBuilder().appendFixed(token);
        String usedRedisValue = redisCacheStringUtils.get(usedCkb);
        if(null!=usedRedisValue){
            log.error("getSmsVerifyCode verifyCodeType token used ,type {} ,verifyCode {},token {}",verifyCodeType,imgVerifyCode,token);
            throw new ServiceException(ServiceError.VERIFY_CODE_IMG_ERROR.getCode(), "图形验证码已过期");
        }

        //检查token是否正确
        whatelyVerifyCodeUtil.checkImgTokenValue(verifyCodeType,token,riskUniqueId,imgVerifyCode);

        //验证通过后，把用过的token存起来，防止token反复使用
        redisCacheStringUtils.set(usedCkb,"1",usedCkb.getDefaultExpireTime(),usedCkb.getTimeUnit());

    }


    @Override
    public void checkSmsSendRule(String mobile){
        //一个手机号码，一分钟内只能发送1条
        RedisKeyBuilder key1m = WhatelyCoreCacheKeyEnum.SMS_CODE_SEND_RULE_1m.obtainKeyBuilder().appendVar(mobile);
        long num_1m = redisCacheStringUtils.incrementWithExpire(key1m,1,key1m.getDefaultExpireTime(),key1m.getTimeUnit());
        if(num_1m > WhatelyVerifyCodeUtil.SMS_SEND_COUNT_1m){
            log.error("checkImgVerifyCodeFromAes checkSmsSendRule exceed COUNT_IN_1m {} count in 1m mobile {}",WhatelyVerifyCodeUtil.SMS_SEND_COUNT_1m,mobile);
            throw new ServiceException(ServiceError.VERIFY_CODE_SMS_ERROR.getCode(),"短信验证码获取过于频繁，请稍后重试");
        }


        //一个手机号码，24小时只能发送30条
        RedisKeyBuilder key12h = WhatelyCoreCacheKeyEnum.SMS_CODE_SEND_RULE_24h.obtainKeyBuilder().appendVar(mobile);
        long num_12h = redisCacheStringUtils.incrementWithExpire(key12h,1,key12h.getDefaultExpireTime(),key12h.getTimeUnit());
        if(num_12h > WhatelyVerifyCodeUtil.SMS_SEND_COUNT_24h){
            log.error("checkImgVerifyCodeFromAes checkSmsSendRule exceed  COUNT_IN_12H {} count in 12 hour mobile {}",WhatelyVerifyCodeUtil.SMS_SEND_COUNT_24h,mobile);
            throw new ServiceException(ServiceError.VERIFY_CODE_SMS_ERROR.getCode(),"验证码获取次数已达上限，请24小时后重试");
        }
    }


    @Override
    public boolean riskCheckPass(String riskUniqueId){
        boolean checkPass;
        //同一个ip，1分钟超过10次，不通过
        String remoteIP  = RemoteIPInterceptor.getRemoteIP();
        //同一个 id，1分钟超过3次，不通过
        RedisKeyBuilder ipRkb = WhatelyCoreCacheKeyEnum.RISK_CTRL_CHECK_IP.obtainKeyBuilder().appendVar(remoteIP);
        long ipNo = redisCacheStringUtils.incrementWithExpire(ipRkb,1,ipRkb.getDefaultExpireTime(),ipRkb.getTimeUnit());

        RedisKeyBuilder idRkb = WhatelyCoreCacheKeyEnum.RISK_CTRL_CHECK_ID.obtainKeyBuilder().appendVar(riskUniqueId);
        long idNo = redisCacheStringUtils.incrementWithExpire(idRkb,1,idRkb.getDefaultExpireTime(),idRkb.getTimeUnit());

        if (ipNo > WhatelyVerifyCodeUtil.RISK_CHECK_IP_ACCOUNT || idNo > WhatelyVerifyCodeUtil.RISK_CHECK_ID_ACCOUNT){
            checkPass = false;
        }else {
            checkPass = true;
        }
        return checkPass;
    }

    @Override
    public int retryTimeInLatestOneMinutes(String riskUniqueId,int maxTryCount){
        RedisKeyBuilder rkb = WhatelyCoreCacheKeyEnum.RISK_CTRL_RETRY_ID.obtainKeyBuilder().appendVar(riskUniqueId);
        Long idNo = redisCacheStringUtils.incrementWithExpire(rkb,1,rkb.getDefaultExpireTime(),rkb.getTimeUnit());
        if(idNo>maxTryCount){
            return maxTryCount+1;
        }
        return idNo.intValue();
    }

    @Override
    public void retryTimeClean(String riskUniqueId){
        RedisKeyBuilder retryRkb = WhatelyCoreCacheKeyEnum.RISK_CTRL_RETRY_ID.obtainKeyBuilder().appendVar(riskUniqueId);
        redisCacheStringUtils.delete(retryRkb);
    }
}



